#include <iostream>
#include <string>
#include <math.h>
#include <cstdarg>
using namespace std;

/*
    可变参数写法一
    va_list
*/
// 省略符形参应该仅仅用于C和C++通用的类型。特别应该注意的是，大多数类类型的对象在传递给省略符形参时都无法正确拷贝。
int sum1(int count, ...)
{
    if (count <= 0)
    {
        return 0;
    }
    /*
    为了实现可变参数列表，首先需要声明一个va_list类型的指针
    va_list类型是在cstdarg头文件里面定义的，该指针用来依次
    指向各个参数
    va_start是一个宏，用来初始化arg_ptr，使其指向列表的第一个
    参数，这个宏的第二个参数是sum函数参数列表省略号前得固定参
    数的名称，用来确定第一个参数的位置
    */
    va_list arg_ptr;
    va_start(arg_ptr, count);
    int CountSum = 0;
    // va_arg是一个宏，返回arg_ptr指向的参数位置，并使arg_ptr递增来指向下一个参数值
    // va_arg宏的第二个参数是需要统计的第一个参数的类型，如果类型不正确，程序也可能会执行，但得到的是无用的数据，arg_ptr将被错误地递增
    for (int i = 0; i < count; ++i)
    {
        CountSum += va_arg(arg_ptr, int);
    }
    // 将va_list类型的指针复位成空值
    va_end(arg_ptr);
    return CountSum;
}

/*
    可变参数写法二,类似于递归，一定要写最后的出口
    模板可变参
*/
template <typename T>
T sum2(T arg)
{
    return arg;
}

template <typename T, typename... Ts>
T sum2(T arg1, Ts... arg_left)
{
    return arg1 + sum2(arg_left...);

    /*
        下面的写法用到c++17编译期if写法，就不需要再单独写一个没有参数的出口
        可变参数模板sum2的所有分支都被instandiated了，并不会考虑上面那个if表达式。
        一个instantiated的代码是否有用是在runtime时决定的，而所有的instantiation是在编译时决定的。
        所以sum2()空参数版本照样被instandiated，而当instandiated的时候并没有发现对应的实现，于是编译期报错。
    */
    // if constexpr(sizeof...(arg_left) > 0)
    // {
    //     return arg1 + sum2(arg_left...);
    // }
    // return arg1;
}

/*
    可变参数写法三,这种方式局限性很大，列表里只能同类型，他的用法跟传vector参数类似，但是比vector跟轻量，而且元素是常量。
    可变参数initializer_list
*/
template <typename T>
T sum3(initializer_list<T> il)
{
    T sum = T();
    for (auto beg = il.begin(); beg != il.end(); ++beg)
        sum += *beg;
    return sum;
}

/*
    可变参数写法四
    fold表达式
    (... op pack)，相当于((( pack1 op pack2 ) op pack3 ) ... op packN)
    (pack op ... )，相当于(pack1 op ( ... ( packN-1 op packN)))
    (init op ... op pack)，相当于((( init op pack1) op pack2) ... op packN)
    (pack op ... op init),相当于(pack1 op ( ... (packN op init)))

*/
template <typename... T>
auto sum4(T... arg_left)
{
    return (... + arg_left);
}

int main()
{
    cout << sum1(5, 1, 2, 3, 4, 5) << endl;
    cout << sum2(1, 2, 3, 4, 5) << endl;
    cout << sum2((string) "hfg", (string) "2", (string) "3", (string) "4", (string) "5") << endl;
    cout << sum3({1, 2, 3, 4, 5}) << endl;
    cout << sum3({(string) "h1", (string) "2", (string) "3"}) << endl;
    //cout<<sum4((string)"gfdg",(string) "1",(string) "2")<<endl;
    cout<<sum4(1,2,3,4)<<endl;
    return 0;
}